package cn.melonlib.cores.jpa.model.bean;

import cn.hutool.core.map.MapUtil;
import cn.melonlib.cores.commons.comp.enums.OrderByType;
import cn.melonlib.cores.commons.model.entity.IEntity;
import cn.melonlib.objects.maps.JsonMap;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.*;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

public interface Searcher<DATA,QUERY extends CharSequence> {
    DATA search(QUERY query, EntityManager entityManager);
    Class<? extends  QUERY> registerType();

    default <ENTITY extends IEntity>Query createSearchQuery(EntityManager entityManager, Class<ENTITY> entityType, JsonMap<? extends Map<String,Object>> filter, JsonMap<OrderByType> orders){
        CriteriaBuilder cb=entityManager.getCriteriaBuilder();
        CriteriaQuery<ENTITY> query=cb.createQuery(entityType);
        Root<ENTITY> root=query.from(entityType);
        query.select(root);
        if(MapUtil.isNotEmpty(orders)){
            List<Order> orderList=orders.entrySet().stream().map(entry->{
                if(Objects.equals(OrderByType.ASC,entry.getValue())){
                    return cb.asc(root.get(entry.getKey()));
                }else{
                    return cb.desc(root.get(entry.getKey()));
                }
            }).collect(Collectors.toList());
            query.orderBy(orderList);
        }
        if(MapUtil.isNotEmpty(filter)){
            List<Predicate> predicates=filter.entrySet().stream().flatMap(field->{
                String fieldName= StringUtils.substringBefore(field.getKey(),"_id");
                return field.getValue().entrySet().stream().filter(f->f.getValue()!=null).filter(f->!Objects.equals(f.getValue(),"")).map(f->{
                    String typeName=f.getKey();
                    switch (typeName){
                        case "eq":
//                            Object value=f.getValue();
//                            if(field.getKey().endsWith("_id")){
//                                Query cquery=entityManager.createQuery("from "+ ReflectUtil.getField(entityType,fieldName).getType().getSimpleName()+" where id=?1");
//                                cquery.setParameter(1, f.getValue());
//                                value=cquery.getSingleResult();
//                            }
                            return cb.equal(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "neq":return cb.notEqual(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "like":return cb.like(root.get(fieldName),"%"+f.getValue()+"%");
                        case "ieq":return cb.equal(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "in":
                            if(f.getValue() instanceof Collection){
                                CriteriaBuilder.In<Object> in=cb.in(root.get(fieldName));
                                ((Collection<?>) f.getValue()).stream().forEach(in::value);
                                return in;
                            }else{
                                return cb.equal(root.get(fieldName),f.getValue());
                            }
                        case "nin":
                            if(f.getValue() instanceof Collection){
                                CriteriaBuilder.In<Object> in=cb.in(root.get(fieldName));
                                ((Collection<?>) f.getValue()).stream().forEach(in::value);
                                return cb.not(in);
                            }else{
                                return cb.notEqual(root.get(fieldName),this.<Object,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                            }
                        case "gt":return cb.gt(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "lt":return cb.lt(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "gte":return cb.ge(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "lte":return cb.le(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "non":return BooleanUtils.isTrue((Boolean) f.getValue())? cb.isNull(root.get(fieldName)):cb.isNotNull(root.get(fieldName));
                        default:  return cb.equal(root.get(fieldName),f.getValue());
                    }
                });
            }).collect(Collectors.toList());
            query.where(predicates.toArray(new Predicate[]{}));
        }

        return entityManager.createQuery(query);
    }

    default <ENTITY extends IEntity>Query createSearchCount(EntityManager entityManager, Class<ENTITY> entityType, JsonMap<? extends Map<String,Object>> filter){
    	
        CriteriaBuilder cb=entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> query=cb.createQuery(Long.class);
        Root<ENTITY> root=query.from(entityType);
        query.select(cb.count(root.get("id")));
        if(MapUtil.isNotEmpty(filter)){
            List<Predicate> predicates=filter.entrySet().stream().flatMap(field->{
                String fieldName= StringUtils.substringBefore(field.getKey(),"_id");
                return field.getValue().entrySet().stream().filter(f->f.getValue()!=null).filter(f->!Objects.equals(f.getValue(),"")).map(f->{
                    String typeName=f.getKey();
                    switch (typeName){
                        case "eq":
//                            Object value=f.getValue();
//                            if(field.getKey().endsWith("_id")){
//                                Query cquery=entityManager.createQuery("from "+ ReflectUtil.getField(entityType,fieldName).getType().getSimpleName()+" where id=?1");
//                                cquery.setParameter(1, f.getValue());
//                                value=cquery.getSingleResult();
//                            }
                            return cb.equal(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "neq":return cb.notEqual(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "like":return cb.like(root.get(fieldName),"%"+f.getValue()+"%");
                        case "ieq":return cb.equal(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "in":
                            if(f.getValue() instanceof Collection){
                                CriteriaBuilder.In<Object> in=cb.in(root.get(fieldName));
                                this.<List,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()).forEach(in::value);
//                                ((Collection<?>) f.getValue()).stream().forEach(in::value);
                                return in;
                            }else{
                                return cb.equal(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                            }
                        case "nin":
                            if(f.getValue() instanceof Collection){
                                CriteriaBuilder.In<Object> in=cb.in(root.get(fieldName));
                                this.<List,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()).forEach(in::value);
//                                ((Collection<?>) f.getValue()).stream().forEach(in::value);
                                return cb.not(in);
                            }else{
                                return cb.notEqual(root.get(fieldName),this.tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                            }
                        case "gt":return cb.gt(root.get(fieldName), this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "lt":return cb.lt(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "gte":return cb.ge(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "lte":return cb.le(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                        case "non":return BooleanUtils.isTrue((Boolean) f.getValue())? cb.isNull(root.get(fieldName)):cb.isNotNull(root.get(fieldName));
                        default:  return cb.equal(root.get(fieldName),this.<Number,ENTITY>tarnsKeyValue(entityManager,entityType,fieldName,f.getValue()));
                    }
                });
            }).collect(Collectors.toList());
            query.where(predicates.toArray(new Predicate[]{}));
        }
        return entityManager.createQuery(query);
    }

    default <VAL,ENTITY extends IEntity<? extends Serializable>> VAL tarnsKeyValue(EntityManager entityManager, Class<ENTITY> entityType,String fieldName, Object value){
        PropertyDescriptor propertyDescriptor= Arrays.stream(ReflectUtils.getBeanGetters(entityType)).filter(pd->pd.getName().equals(fieldName)).reduce(null,(a, b) -> b);
        if(propertyDescriptor==null){
            return null;
        }
        Class cls=propertyDescriptor.getPropertyType();

        if(value instanceof Collection){
            if(cls.isEnum()){
                return (VAL) ((Collection<?>) value).stream().map(v->Enum.valueOf(cls,value.toString())).collect(Collectors.toList());
            }else if(IEntity.class.isAssignableFrom(cls)){
                Query query=entityManager.createQuery("from "+cls.getName()+" where "+fieldName +" in ("+((Collection<?>) value)
                        .stream().reduce(new ArrayList<Integer>(((Collection<?>) value).size()),(l,v)->{
                            l.add(l.size()+1);
                            return l;
                        },(l,v)->null).stream().map(i->"?"+i).collect(Collectors.joining(","))
                        +")");
                ((Collection<?>) value).stream().reduce(1,(row,v)->{
                    query.setParameter(row,v);
                    return row+1;
                },(row,v)->null);
                return (VAL) query.getResultList();
            }else{
                return (VAL) value;
            }
        }
        Assert.isTrue(!Collection.class.isAssignableFrom(cls),"查询条件不能为集合对象");
        if(cls.isEnum()){
            return (VAL) Enum.valueOf(cls,value.toString());
        }else if(IEntity.class.isAssignableFrom(cls)){
            List<VAL> list= entityManager.createQuery("from "+cls.getName()+" where id=?1").setParameter(1,value).getResultList();
            if(list.isEmpty()){
                return null;
            }else{
                return list.get(0);
            }
        }else if(Double.class.isAssignableFrom(cls)||Double.TYPE.isAssignableFrom(cls)){
            if(value instanceof Number){
                return (VAL) Double.valueOf(((Number) value).doubleValue());
            }else{
                return (VAL) NumberUtils.createDouble(value.toString());
            }
        }else if(Boolean.class.isAssignableFrom(cls)||Double.TYPE.isAssignableFrom(cls)){
            if(value instanceof Boolean){
                return (VAL)value;
            }else{
                return (VAL) Boolean.valueOf(BooleanUtils.toBoolean(value.toString()));
            }
        }else if(Long.class.isAssignableFrom(cls)||Long.TYPE.isAssignableFrom(cls)){
            if(value instanceof Number){
                return (VAL) Long.valueOf(((Number) value).longValue());
            }else{
                return (VAL) NumberUtils.createLong(value.toString());
            }
        }else if(Integer.class.isAssignableFrom(cls)||Integer.TYPE.isAssignableFrom(cls)){
            if(value instanceof Number){
                return (VAL) Integer.valueOf(((Number) value).intValue());
            }else{
                return (VAL) NumberUtils.createInteger(value.toString());
            }
        }else if(Float.class.isAssignableFrom(cls)||Float.TYPE.isAssignableFrom(cls)){
            if(value instanceof Number){
                return (VAL) Float.valueOf(((Number) value).floatValue());
            }else{
                return (VAL) NumberUtils.createFloat(value.toString());
            }
        }else{
            return (VAL) value;
        }
    }

}
